package components

import (
	"fmt"
	"strconv"
	"strings"

	"github.com/1Panel-dev/1Panel/agent/utils/re"
)

type Location struct {
	Modifier         string
	Match            string
	Cache            bool
	ProxyPass        string
	Host             string
	CacheTime        int
	CacheUint        string
	Comment          string
	Directives       []IDirective
	Line             int
	Parameters       []string
	Replaces         map[string]string
	ServerCacheTime  int
	ServerCacheUint  string
	Cors             bool
	AllowMethods     string
	AllowHeaders     string
	AllowOrigins     string
	AllowCredentials bool
	Preflight        bool
}

func (l *Location) GetCodeBlock() string {
	return ""
}

func NewLocation(directive IDirective) *Location {
	location := &Location{
		Modifier: "",
		Match:    "",
	}
	directives := make([]IDirective, 0)
	if len(directive.GetParameters()) == 0 {
		panic("no enough parameter for location")
	}
	for _, dir := range directive.GetBlock().GetDirectives() {
		directives = append(directives, dir)
		params := dir.GetParameters()
		switch dir.GetName() {
		case "proxy_pass":
			location.ProxyPass = params[0]
		case "proxy_set_header":
			if params[0] == "Host" {
				location.Host = params[1]
			}
		case "proxy_cache":
			location.Cache = true
		case "if":
			if params[0] == "(" && params[1] == "$uri" && params[2] == "~*" {
				dirs := dir.GetBlock().GetDirectives()
				for _, di := range dirs {
					if di.GetName() == "expires" {
						matches := re.GetRegex(re.NumberWordPattern).FindStringSubmatch(di.GetParameters()[0])
						if matches == nil {
							continue
						}
						cacheTime, err := strconv.Atoi(matches[1])
						if err != nil {
							continue
						}
						unit := matches[2]
						location.CacheUint = unit
						location.CacheTime = cacheTime
					}
				}
			}
			if params[0] == "(" && params[1] == "$request_method" && params[2] == `=` && params[3] == `'OPTIONS'` && params[4] == ")" {
				location.Preflight = true
			}
		case "proxy_cache_valid":
			timeParam := params[len(params)-1]
			matches := re.GetRegex(re.NumberWordPattern).FindStringSubmatch(timeParam)
			if matches == nil {
				continue
			}

			cacheTime, err := strconv.Atoi(matches[1])
			if err != nil {
				continue
			}
			unit := matches[2]

			location.ServerCacheTime = cacheTime
			location.ServerCacheUint = unit
		case "sub_filter":
			if location.Replaces == nil {
				location.Replaces = make(map[string]string, 0)
			}
			location.Replaces[strings.Trim(params[0], "\"")] = strings.Trim(params[1], "\"")
		case "add_header":
			if params[0] == "Access-Control-Allow-Origin" {
				location.Cors = true
				location.AllowOrigins = params[1]
			}
			if params[0] == "Access-Control-Allow-Methods" {
				location.AllowMethods = params[1]
			}
			if params[0] == "Access-Control-Allow-Headers" {
				location.AllowHeaders = params[1]
			}
			if params[0] == "Access-Control-Allow-Credentials" && params[1] == "true" {
				location.AllowCredentials = true
			}
		}
	}

	params := directive.GetParameters()
	if len(params) == 1 {
		location.Match = params[0]
	} else if len(params) == 2 {
		location.Match = params[1]
		location.Modifier = params[0]
	}
	location.Parameters = directive.GetParameters()
	location.Line = directive.GetLine()
	location.Comment = directive.GetComment()
	location.Directives = directives
	return location
}

func (l *Location) GetName() string {
	return "location"
}

func (l *Location) GetParameters() []string {
	return l.Parameters
}

func (l *Location) GetBlock() IBlock {
	return l
}

func (l *Location) GetComment() string {
	return l.Comment
}

func (l *Location) GetLine() int {
	return l.Line
}

func (l *Location) GetDirectives() []IDirective {
	return l.Directives
}

func (l *Location) FindDirectives(directiveName string) []IDirective {
	directives := make([]IDirective, 0)
	for _, directive := range l.Directives {
		if directive.GetName() == directiveName {
			directives = append(directives, directive)
		}
		if directive.GetBlock() != nil {
			directives = append(directives, directive.GetBlock().FindDirectives(directiveName)...)
		}
	}
	return directives
}

func (l *Location) UpdateDirective(key string, params []string) {
	if key == "" || len(params) == 0 {
		return
	}
	directives := l.Directives
	index := -1
	for i, dir := range directives {
		if dir.GetName() == key {
			if IsRepeatKey(key) {
				oldParams := dir.GetParameters()
				if !(len(oldParams) > 0 && oldParams[0] == params[0]) {
					continue
				}
			}
			index = i
			break
		}
	}
	newDirective := &Directive{
		Name:       key,
		Parameters: params,
	}
	if index > -1 {
		directives[index] = newDirective
	} else {
		directives = append(directives, newDirective)
	}
	l.Directives = directives
}

// RemoveDirective removes a directive by its name and optional FIRST parameter match
func (l *Location) RemoveDirective(key string, params []string) {
	directives := l.Directives
	var newDirectives []IDirective
	for _, dir := range directives {
		if dir.GetName() == key {
			if len(params) > 0 {
				oldParams := dir.GetParameters()
				if oldParams[0] == params[0] {
					continue
				}
			} else {
				continue
			}
		}
		newDirectives = append(newDirectives, dir)
	}
	l.Directives = newDirectives
}

// RemoveDirectiveByFullParams removes a directive by its name and full parameter match
func (l *Location) RemoveDirectiveByFullParams(key string, params []string) {
	directives := l.Directives
	var newDirectives []IDirective
	for _, dir := range directives {
		if dir.GetName() == key {
			oldParams := dir.GetParameters()
			if len(oldParams) == len(params) {
				allMatch := true
				for i, param := range params {
					if oldParams[i] != param {
						allMatch = false
						break
					}
				}
				if allMatch {
					continue
				}
			}
		}
		newDirectives = append(newDirectives, dir)
	}
	l.Directives = newDirectives
}

func (l *Location) ChangePath(Modifier string, Match string) {
	if Match != "" && Modifier != "" {
		l.Parameters = []string{Modifier, Match}
	}
	if Match != "" && Modifier == "" {
		l.Parameters = []string{Match}
	}
	l.Modifier = Modifier
	l.Match = Match
}

func (l *Location) AddBrowserCache(cacheTime int, cacheUint string) {
	l.RemoveDirective("add_header", []string{"Cache-Control", "no-cache"})
	l.RemoveDirectiveByFullParams("if", []string{"(", "$uri", "~*", `"\.(gif|png|jpg|css|js|woff|woff2)$"`, ")"})
	l.RemoveDirectiveByFullParams("if", []string{"(", "$uri", "~*", `"\.(gif|png|jpg|css|js|woff|woff2|jpeg|svg|webp|avif)$"`, ")"})
	directives := l.GetDirectives()
	newDir := &Directive{
		Name:       "if",
		Parameters: []string{"(", "$uri", "~*", `"\.(gif|png|jpg|css|js|woff|woff2|jpeg|svg|webp|avif)$"`, ")"},
		Block:      &Block{},
	}
	block := &Block{}
	block.Directives = append(block.Directives, &Directive{
		Name:       "expires",
		Parameters: []string{strconv.Itoa(cacheTime) + cacheUint},
	})
	newDir.Block = block
	directives = append(directives, newDir)
	l.Directives = directives
	l.CacheTime = cacheTime
	l.CacheUint = cacheUint
}

func (l *Location) AddServerCache(cacheKey string, serverCacheTime int, serverCacheUint string) {
	l.UpdateDirective("proxy_ignore_headers", []string{"Set-Cookie", "Cache-Control", "expires"})
	l.UpdateDirective("proxy_cache", []string{cacheKey})
	l.UpdateDirective("proxy_cache_key", []string{"$host$uri$is_args$args"})
	l.UpdateDirective("proxy_cache_valid", []string{"200", "304", "301", "302", strconv.Itoa(serverCacheTime) + serverCacheUint})
	l.Cache = true
	l.ServerCacheTime = serverCacheTime
	l.ServerCacheUint = serverCacheUint
}

func (l *Location) RemoveBrowserCache() {
	l.RemoveDirectiveByFullParams("if", []string{"(", "$uri", "~*", `"\.(gif|png|jpg|css|js|woff|woff2)$"`, ")"})
	l.RemoveDirectiveByFullParams("if", []string{"(", "$uri", "~*", `"\.(gif|png|jpg|css|js|woff|woff2|jpeg|svg|webp|avif)$"`, ")"})
	l.UpdateDirective("add_header", []string{"Cache-Control", "no-cache"})
	l.CacheTime = 0
	l.CacheUint = ""
}

func (l *Location) RemoveServerCache(cacheKey string) {
	l.RemoveDirective("proxy_ignore_headers", []string{"Set-Cookie", "Cache-Control", "expires"})
	l.RemoveDirective("proxy_cache", []string{cacheKey})
	l.RemoveDirective("proxy_cache_key", []string{"$host$uri$is_args$args"})
	l.RemoveDirective("proxy_cache_valid", []string{"200"})
	l.Cache = false
	l.ServerCacheTime = 0
	l.ServerCacheUint = ""
}

func (l *Location) AddSubFilter(subFilters map[string]string) {
	l.RemoveDirective("sub_filter", []string{})
	l.Replaces = subFilters
	for k, v := range subFilters {
		l.UpdateDirective("sub_filter", []string{fmt.Sprintf(`"%s"`, k), fmt.Sprintf(`"%s"`, v)})
	}
	l.UpdateDirective("proxy_set_header", []string{"Accept-Encoding", `""`})
	l.UpdateDirective("sub_filter_once", []string{"off"})
	l.UpdateDirective("sub_filter_types", []string{"*"})
}

func (l *Location) RemoveSubFilter() {
	l.RemoveDirective("sub_filter", []string{})
	l.RemoveDirective("proxy_set_header", []string{"Accept-Encoding", `""`})
	l.RemoveDirective("sub_filter_once", []string{"off"})
	l.RemoveDirective("sub_filter_types", []string{"*"})
	l.Replaces = nil
}

func (l *Location) AddCorsOption() {
	l.RemoveCorsOption()
	newDir := &Directive{
		Name:       "if",
		Parameters: []string{"(", "$request_method", "=", "'OPTIONS'", ")"},
		Block:      &Block{},
	}
	block := &Block{}
	block.AppendDirectives(&Directive{
		Name:       "add_header",
		Parameters: []string{"Access-Control-Max-Age", "1728000"},
	})
	block.AppendDirectives(&Directive{
		Name:       "add_header",
		Parameters: []string{"Content-Type", "'text/plain;charset=UTF-8'"},
	})
	block.AppendDirectives(&Directive{
		Name:       "add_header",
		Parameters: []string{"Content-Length", "0"},
	})
	block.AppendDirectives(&Directive{
		Name:       "return",
		Parameters: []string{"204"},
	})
	newDir.Block = block
	directives := l.GetDirectives()
	directives = append(directives, newDir)
	l.Directives = directives
}

func (l *Location) RemoveCorsOption() {
	l.RemoveDirectiveByFullParams("if", []string{"(", "$request_method", "=", "'OPTIONS'", ")"})
}
